package edu.gatech.ors.mrsim.core;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;

import org.apache.log4j.Logger;

import edu.gatech.ors.mrsim.io.conf.ConfigurableClass;
import edu.gatech.ors.mrsim.io.conf.Configuration;
import edu.gatech.ors.mrsim.io.logging.Logging;

public abstract class Module extends ConfigurableClass {
	protected Simulation simulation;
	protected final Logger LOGGER = Logging.getInstance().getClassLogger(this.getClass());
	private HashMap<Class<?>, Method> eventHandlers;
	private HashMap<Class<?>, Method> hierarchyLeafClasses;

	public Module(Simulation simulation, Configuration config) {
		super(config);
		this.simulation = simulation;
		eventHandlers = new HashMap<Class<?>, Method>();
		hierarchyLeafClasses = new HashMap<Class<?>, Method>();
		for (Method method : getClass().getMethods()) {
			Class<?>[] parameterTypes = method.getParameterTypes();
			if (parameterTypes.length == 1) {
				Class<?> parameter = parameterTypes[0];
				if (Event.class.isAssignableFrom(parameter)) {
					String name = method.getName();
					String className = parameter.getName();
					className = className.substring(className.lastIndexOf('.') + 1);
					if (name.equals("handle" + className)) {
						LOGGER.debug("Module " + this.getClass().getName() + " has handler for " + parameter.getName());
						eventHandlers.put(parameter, method);
					}
				}
			}
		}
		for (Class<?> eventClass : eventHandlers.keySet()) {
			boolean assignable = false;
			for (Class<?> otherEventClass : eventHandlers.keySet()) {
				if (eventClass != otherEventClass && eventClass.getClass().isAssignableFrom(otherEventClass)) {
					assignable = true;
					break;
				}
			}
			if (!assignable) {
				hierarchyLeafClasses.put(eventClass, eventHandlers.get(eventClass));
			}
		}
	}

	protected Simulation getSimulation() {
		return simulation;
	}

	protected final void propagateEvent(Event<?> event) {
		List<Event<?>> moreEvents = event.getSourceNode().respondToEventCompletion(event);
		simulation.enqueueEvents(moreEvents);
	}

	private final Method getEventHandler(Event<?> event) {
		Class<?> eventClass = event.getClass();
		if (eventHandlers.containsKey(eventClass))
			return eventHandlers.get(eventClass);
		Method method = null;
		for (Class<?> knownEventClass : hierarchyLeafClasses.keySet()) {
			if (event.getClass().isAssignableFrom(knownEventClass)) {
				Method eventMethod = eventHandlers.get(knownEventClass);
				hierarchyLeafClasses.put(eventClass.getClass(), eventMethod);
				while (!eventHandlers.containsKey(eventClass)) {
					eventHandlers.put(eventClass, eventMethod);
					eventClass = eventClass.getSuperclass();
				}
			}
		}
		return method;
	}

	protected final boolean handleEvent(Event<?> event) {
		Method handler = getEventHandler(event);
		if (handler != null) {
			try {

				handler.invoke(this, event);
				return true;
			} catch (IllegalAccessException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IllegalArgumentException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (InvocationTargetException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return false;
	}
}
